/*
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * Licensed under the LGPL v2.1, see the file LICENSE in base directory.
 */

#include "cdbc.h"

#include <sys/time.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

#include <mysql.h>

#define _DEBUG		0

#if _DEBUG
#define _TRACE(_format_,...) { struct timeval now_tv; struct tm now_tm; char datetime_buf[10+1+8+1]=""; gettimeofday(&now_tv,NULL); localtime_r(&(now_tv.tv_sec),&now_tm); strftime(datetime_buf,sizeof(datetime_buf),"%Y-%m-%d %H:%M:%S",&now_tm); printf( "TRACE - %d:%010lu - %s - %s:%d:%s - "_format_"\n" , getpid() , (unsigned long)pthread_self() , datetime_buf , __FILE__,__LINE__,__FUNCTION__ , ##__VA_ARGS__ ); fflush(stdout); }
#else
#define _TRACE(_format_,...)
#endif

struct DatabaseConnection
{
	MYSQL		*mysql ;
} ;

funcInitDriverLibrary InitDriverLibrary ;
funcEndDriverLibrary EndDriverLibrary ;
funcConnectToDatabase ConnectToDatabase ;
funcDisconnectFromDatabase DisconnectFromDatabase ;
funcExecuteSql ExecuteSql ;
funcAutoCommitTransaction AutoCommitTransaction ;
funcCommitTransaction CommitTransaction ;
funcRollbackTransaction RollbackTransaction ;

char *strcasestr(const char *haystack, const char *needle);

int InitDriverLibrary()
{
	mysql_library_init( 0 , NULL , NULL );
	
	return 0;
}

int EndDriverLibrary()
{
	mysql_library_end();
	
	return 0;
}

struct DatabaseConnection *ConnectToDatabase( char *db_host , int db_port , char *db_user , char *db_pass , char *db_name )
{
	struct DatabaseConnection	*db_conn = NULL ;
	MYSQL				*p_mysql = NULL ;
	char				*p = NULL ;
	
	if( db_host == NULL || db_port <= 0 )
	{
		DBCSetLastErrno( CDBC_ERROR_PARAMETER );
		return NULL;
	}
	
	db_conn = (struct DatabaseConnection *)malloc( sizeof(struct DatabaseConnection) ) ;
	if( db_conn == NULL )
	{
		DBCSetLastErrno( CDBC_ERROR_ALLOC );
		return NULL;
	}
	memset( db_conn , 0x00 , sizeof(struct DatabaseConnection) );
	
	db_conn->mysql = mysql_init( NULL ) ;
	if( db_conn->mysql == NULL )
	{
		DisconnectFromDatabase( & db_conn );
		DBCSetLastErrno( CDBC_ERROR_ALLOC );
		return NULL;
	}
	
	p_mysql = mysql_real_connect( db_conn->mysql , db_host , db_user , db_pass , db_name , db_port , NULL , 0 ) ;
	if( p_mysql == NULL )
	{
		DisconnectFromDatabase( & db_conn );
		DBCSetLastErrno( CDBC_ERROR_CONNECT );
		return NULL;
	}
	
	p = setlocale( LC_ALL , NULL ) ;
	if( strcasestr( p , "GB2312" ) == 0 || strcasestr( p , "GBK" ) == 0 || strcasestr( p , "GB18030" ) == 0 )
		mysql_set_character_set( db_conn->mysql , "gbk" );
	else if( strcasestr( p , "UTF8" ) == 0 )
		mysql_set_character_set( db_conn->mysql , "utf8" );
	
	return db_conn;
}

void DisconnectFromDatabase( struct DatabaseConnection **db_conn )
{
	if( db_conn == NULL )
	{
		DBCSetLastErrno( CDBC_ERROR_PARAMETER );
		return;
	}
	
	if( (*db_conn) )
	{
		if( (*db_conn)->mysql )
		{
			mysql_close( (*db_conn)->mysql ); (*db_conn)->mysql = NULL ;
		}
		
		free( (*db_conn) ); (*db_conn) = NULL ;
	}
	
	return;
}

static int GetMysqlFieldLength( enum enum_field_types type )
{
	if( type == MYSQL_TYPE_TINY )
		return sizeof(int8_t);
	else if( type == MYSQL_TYPE_SHORT )
		return sizeof(int16_t);
	else if( type == MYSQL_TYPE_LONG || type == MYSQL_TYPE_INT24 )
		return sizeof(int32_t);
	else if( type == MYSQL_TYPE_LONGLONG )
		return sizeof(int64_t);
	else if( type == MYSQL_TYPE_FLOAT )
		return sizeof(float);
	else if( type == MYSQL_TYPE_DOUBLE )
		return sizeof(double);
	else if( type == MYSQL_TYPE_DECIMAL || type == MYSQL_TYPE_NEWDECIMAL )
		return sizeof(double);
	else if( type == MYSQL_TYPE_STRING )
		return 0;
	else if( type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_VAR_STRING )
		return 0;
	else if( type == MYSQL_TYPE_DATE )
		return sizeof(MYSQL_TIME);
	else if( type == MYSQL_TYPE_TIME )
		return sizeof(MYSQL_TIME);
	else if( type == MYSQL_TYPE_DATETIME )
		return sizeof(MYSQL_TIME);
	else if( type == MYSQL_TYPE_TIMESTAMP )
		return sizeof(MYSQL_TIME);
	else if( type == MYSQL_TYPE_BLOB )
		return 0;
	else
		return -1;
}

static enum FieldType ConvertMysqlFieldType( enum enum_field_types type )
{
	if( type == MYSQL_TYPE_TINY )
		return CDBC_FIELDTYPE_INT8;
	else if( type == MYSQL_TYPE_SHORT )
		return CDBC_FIELDTYPE_INT16;
	else if( type == MYSQL_TYPE_LONG || type == MYSQL_TYPE_INT24 )
		return CDBC_FIELDTYPE_INT32;
	else if( type == MYSQL_TYPE_LONGLONG )
		return CDBC_FIELDTYPE_INT64;
	else if( type == MYSQL_TYPE_FLOAT )
		return CDBC_FIELDTYPE_FLOAT;
	else if( type == MYSQL_TYPE_DOUBLE )
		return CDBC_FIELDTYPE_DOUBLE;
	else if( type == MYSQL_TYPE_DECIMAL || type == MYSQL_TYPE_NEWDECIMAL )
		return CDBC_FIELDTYPE_VARCHAR;
	else if( type == MYSQL_TYPE_STRING )
		return CDBC_FIELDTYPE_CHAR;
	else if( type == MYSQL_TYPE_VARCHAR || type == MYSQL_TYPE_VAR_STRING || type == MYSQL_TYPE_BLOB )
		return CDBC_FIELDTYPE_VARCHAR;
	else if( type == MYSQL_TYPE_DATE )
		return CDBC_FIELDTYPE_DATE;
	else if( type == MYSQL_TYPE_TIME )
		return CDBC_FIELDTYPE_TIME;
	else if( type == MYSQL_TYPE_DATETIME )
		return CDBC_FIELDTYPE_DATETIME;
	else if( type == MYSQL_TYPE_TIMESTAMP )
		return CDBC_FIELDTYPE_TIMESTAMP;
	else
		return CDBC_FIELDTYPE_OTHER;
}

static enum enum_field_types ConvertCdbcFieldType( enum FieldType type )
{
	if( type == CDBC_FIELDTYPE_INT8 )
		return MYSQL_TYPE_TINY;
	else if( type == CDBC_FIELDTYPE_INT16 )
		return MYSQL_TYPE_SHORT;
	else if( type == CDBC_FIELDTYPE_INT32 )
		return MYSQL_TYPE_LONG;
	else if( type == CDBC_FIELDTYPE_INT64 )
		return MYSQL_TYPE_LONGLONG;
	else if( type == CDBC_FIELDTYPE_FLOAT )
		return MYSQL_TYPE_FLOAT;
	else if( type == CDBC_FIELDTYPE_DOUBLE )
		return MYSQL_TYPE_DOUBLE;
	else if( type == CDBC_FIELDTYPE_DECIMAL )
		return MYSQL_TYPE_DECIMAL;
	else if( type == CDBC_FIELDTYPE_CHAR )
		return MYSQL_TYPE_STRING;
	else if( type == CDBC_FIELDTYPE_VARCHAR )
		return MYSQL_TYPE_VAR_STRING;
	else if( type == CDBC_FIELDTYPE_DATE )
		return MYSQL_TYPE_DATE;
	else if( type == CDBC_FIELDTYPE_TIME )
		return MYSQL_TYPE_TIME;
	else if( type == CDBC_FIELDTYPE_DATETIME )
		return MYSQL_TYPE_DATETIME;
	else if( type == CDBC_FIELDTYPE_TIMESTAMP )
		return MYSQL_TYPE_TIMESTAMP;
	else
		return MYSQL_TYPE_STRING;
}

void ExecuteSql( struct DatabaseConnection *db_conn , char *sql , struct FieldBind *binds_array , int binds_array_length , int *row_count , int *col_count , struct FieldInfo **query_field_set , char ***query_result_set , int *affected_count )
{
	MYSQL_STMT		*mysql_stmt = NULL ;
	struct FieldBind	*binds_array_offsetptr = NULL ;
	unsigned long		mysql_binds_array_length ;
	MYSQL_BIND		*mysql_binds_array = NULL ;
	MYSQL_BIND		*mysql_binds_array_offsetptr = NULL ;
	/* my_bool		*mysql_binds_null_array = NULL ; */
	MYSQL_RES		*mysql_res = NULL ;
	size_t			alloc_size ;
	my_ulonglong		mysql_col_count , mysql_col_index ;
	my_ulonglong		mysql_row_count , mysql_row_index ;
	size_t			set_index ;
	struct FieldInfo	*mysql_query_field_set = NULL ;
	MYSQL_FIELD		*mysql_fields = NULL ;
	char			**mysql_query_result_set = NULL ;
	int			field_buffer_length ;
	int			record_buffer_size ;
	char			*record_buffer = NULL ;
	char			*record_buffer_offsetptr = NULL ;
	my_bool			*null_buffer = NULL ;
	char			*null_buffer_offsetptr = NULL ;
	int			nret = 0 ;
	
	DBCFreeSqlResult( query_field_set , query_result_set );
	
	mysql_stmt = mysql_stmt_init( db_conn->mysql ) ;
	_TRACE( "mysql_stmt_init return[%p]" , mysql_stmt )
	if( mysql_stmt == NULL )
	{
		DBCSetLastErrno( CDBC_ERROR_QUERY );
		DBCSetLastNativeErrno( mysql_errno(db_conn->mysql) );
		DBCSetLastNativeError( (char*)mysql_error(db_conn->mysql) );
		DBCSetLastSqlState( (char*)mysql_sqlstate(db_conn->mysql) );
		return;
	}
	
	nret = mysql_stmt_prepare( mysql_stmt , sql , strlen(sql) ) ;
	_TRACE( "mysql_stmt_prepare[%s] return[%d]" , sql , nret )
	if( nret )
	{
		DBCSetLastErrno( CDBC_ERROR_QUERY );
		DBCSetLastNativeErrno( mysql_stmt_errno(mysql_stmt) );
		DBCSetLastNativeError( (char*)mysql_stmt_error(mysql_stmt) );
		DBCSetLastSqlState( (char*)mysql_stmt_sqlstate(mysql_stmt) );
		mysql_stmt_close( mysql_stmt );
		return;
	}
	
	mysql_binds_array_length = mysql_stmt_param_count( mysql_stmt ) ;
	_TRACE( "mysql_stmt_param_count[%ld]" , mysql_binds_array_length )
	if( mysql_binds_array_length > 0 )
	{
		int	i ;
		
		if( mysql_binds_array_length != binds_array_length )
		{
			_TRACE( "return CDBC_ERROR_BIND_ARRAY_COUNT_NOT_MATCHED , mysql_binds_array_length[%ld] binds_array_length[%d]" , mysql_binds_array_length , binds_array_length )
			DBCSetLastErrno( CDBC_ERROR_BIND_ARRAY_COUNT_NOT_MATCHED );
			mysql_stmt_close( mysql_stmt );
			return;
		}
		
		mysql_binds_array = (MYSQL_BIND *)malloc( sizeof(MYSQL_BIND) * mysql_binds_array_length ) ;
		if( mysql_binds_array == NULL )
		{
			_TRACE( "malloc failed , errno[%d]" , errno )
			DBCSetLastErrno( CDBC_ERROR_ALLOC );
			mysql_stmt_close( mysql_stmt );
			return;
		}
		memset( mysql_binds_array , 0x00 , sizeof(MYSQL_BIND) * mysql_binds_array_length );
		
#define IF_CDBCTYPE_THEN_TRACE(_CDBC_TYPE_,_TYPE_FORMAT_,_C_TYPE_) \
	if( binds_array_offsetptr->buffer_type == _CDBC_TYPE_ ) \
	{ \
		_TRACE( "bind sql param - buffer_type[%d]->[%d] buffer["_TYPE_FORMAT_"]" , binds_array_offsetptr->buffer_type,mysql_binds_array_offsetptr->buffer_type , *(_C_TYPE_*)(mysql_binds_array_offsetptr->buffer) ) \
	} \

		for( i = 0 , binds_array_offsetptr = binds_array , mysql_binds_array_offsetptr = mysql_binds_array ; i < mysql_binds_array_length ; i++ , binds_array_offsetptr++ , mysql_binds_array_offsetptr++ )
		{
			mysql_binds_array_offsetptr->buffer_type = ConvertCdbcFieldType( binds_array_offsetptr->buffer_type ) ;
			mysql_binds_array_offsetptr->buffer = binds_array_offsetptr->buffer ;
			mysql_binds_array_offsetptr->buffer_length = binds_array_offsetptr->buffer_length ;
			IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_INT8 , "%"PRIi8 , int8_t )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_INT16 , "%"PRIi16 , int16_t )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_INT32 , "%"PRIi32 , int32_t )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_INT64 , "%"PRIi64 , int64_t )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_FLOAT , "%f" , float )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_DOUBLE , "%lf" , double )
			else IF_CDBCTYPE_THEN_TRACE( CDBC_FIELDTYPE_DECIMAL , "%lf" , double )
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_CHAR || binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_VARCHAR )
			{
				_TRACE( "bind sql param - buffer_type[%d]->[%d] buffer[%s]" , binds_array_offsetptr->buffer_type,mysql_binds_array_offsetptr->buffer_type , (char*)(mysql_binds_array_offsetptr->buffer) )
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_DATETIME )
			{
				struct tm	*p_tm = (struct tm *)(mysql_binds_array_offsetptr->buffer) ;
				char		buf[ 10+1+8 + 1] = "" ;
				MYSQL_TIME	*my_time = NULL ;
				strftime( buf , sizeof(buf) , "%Y-%m-%d %H:%M:%S" , p_tm );
				_TRACE( "bind sql param - buffer_type[%d]->[%d] buffer[%s]" , binds_array_offsetptr->buffer_type,mysql_binds_array_offsetptr->buffer_type , buf )
				
				my_time = (MYSQL_TIME *)malloc( sizeof(MYSQL_TIME) ) ;
				if( my_time == NULL )
				{
					_TRACE( "malloc failed , errno[%d]" , errno )
					mysql_stmt_close( mysql_stmt );
					DBCSetLastErrno( CDBC_ERROR_ALLOC );
					return;
				}
				memset( my_time , 0x00 , sizeof(MYSQL_TIME) );
				my_time->year = p_tm->tm_year+1900 ;
				my_time->month = p_tm->tm_mon+1 ;
				my_time->day = p_tm->tm_mday ;
				my_time->hour = p_tm->tm_hour ;
				my_time->minute = p_tm->tm_min ;
				my_time->second = p_tm->tm_sec ;
				mysql_binds_array_offsetptr->buffer = (char*)my_time ;
				binds_array_offsetptr->buffer_alloced = mysql_binds_array_offsetptr->buffer ;
			}
			else
			{
				_TRACE( "unknow cdbc_type[%d]" , binds_array_offsetptr->buffer_type )
				free( mysql_binds_array );
				DBCSetLastErrno( CDBC_ERROR_BIND_PARAM );
				DBCSetLastNativeErrno( mysql_stmt_errno(mysql_stmt) );
				DBCSetLastNativeError( (char*)mysql_stmt_error(mysql_stmt) );
				DBCSetLastSqlState( (char*)mysql_stmt_sqlstate(mysql_stmt) );
				mysql_stmt_close( mysql_stmt );
				return;
			}
		}
		
		nret = mysql_stmt_bind_param( mysql_stmt , mysql_binds_array ) ;
		_TRACE( "mysql_stmt_bind_param return[%d]" , nret )
		if( nret )
		{
			free( mysql_binds_array );
			DBCSetLastErrno( CDBC_ERROR_BIND_PARAM );
			DBCSetLastNativeErrno( mysql_stmt_errno(mysql_stmt) );
			DBCSetLastNativeError( (char*)mysql_stmt_error(mysql_stmt) );
			DBCSetLastSqlState( (char*)mysql_stmt_sqlstate(mysql_stmt) );
			mysql_stmt_close( mysql_stmt );
			return;
		}
		
		free( mysql_binds_array );
	}
	
	nret = mysql_stmt_execute( mysql_stmt ) ;
	_TRACE( "mysql_stmt_execute return[%d]" , nret )
	if( nret )
	{
		DBCSetLastErrno( CDBC_ERROR_QUERY );
		DBCSetLastNativeErrno( mysql_stmt_errno(mysql_stmt) );
		DBCSetLastNativeError( (char*)mysql_stmt_error(mysql_stmt) );
		DBCSetLastSqlState( (char*)mysql_stmt_sqlstate(mysql_stmt) );
		mysql_stmt_close( mysql_stmt );
		return;
	}
	
	nret = mysql_stmt_store_result( mysql_stmt ) ;
	_TRACE( "mysql_stmt_store_result reutrn[%d]" , nret )
	if( nret )
	{
		DBCSetLastErrno( CDBC_ERROR_QUERY );
		DBCSetLastNativeErrno( mysql_stmt_errno(mysql_stmt) );
		DBCSetLastNativeError( (char*)mysql_stmt_error(mysql_stmt) );
		DBCSetLastSqlState( (char*)mysql_stmt_sqlstate(mysql_stmt) );
		mysql_stmt_close( mysql_stmt );
		return;
	}
	
	mysql_res = mysql_stmt_result_metadata( mysql_stmt ) ;
	_TRACE( "mysql_stmt_result_metadata return[%p]" , mysql_res )
	if( mysql_res == NULL )
	{
		mysql_row_count = 0 ;
		mysql_col_count = 0 ;
		mysql_query_field_set = NULL ;
		mysql_query_result_set = NULL ;
	}
	else
	{
		mysql_row_count = mysql_stmt_num_rows( mysql_stmt ) ;
		_TRACE( "mysql_stmt_num_rows return mysql_row_count[%lu]" , (unsigned long)mysql_row_count )
		mysql_col_count = mysql_num_fields( mysql_res ) ;
		_TRACE( "  mysql_num_fields return mysql_col_count[%ld]" , (long)mysql_col_count )
		
		if( mysql_col_count > 0 && query_field_set )
		{
			alloc_size = sizeof(struct FieldInfo) * (mysql_col_count+1) ;
			mysql_query_field_set = (struct FieldInfo *)malloc( alloc_size ) ;
			if( mysql_query_field_set == NULL )
			{
				_TRACE( "  InitResizableBuffer failed , errno[%d]" , errno )
				DBCSetLastErrno( CDBC_ERROR_ALLOC );
				mysql_free_result( mysql_res );
				mysql_stmt_close( mysql_stmt );
				return;
			}
			memset( mysql_query_field_set , 0x00 , alloc_size );
			mysql_query_field_set[mysql_col_count].field_length = mysql_row_count ;
			
			record_buffer_size = 0 ;
			mysql_fields = mysql_fetch_fields( mysql_res ) ;
			for( mysql_col_index = 0 ; mysql_col_index < mysql_col_count ; mysql_col_index++ )
			{
				mysql_query_field_set[mysql_col_index].field_name = strdup( mysql_fields[mysql_col_index].name ) ;
				if( mysql_query_field_set[mysql_col_index].field_name == NULL )
				{
					_TRACE( "  AppendResizableBuffer failed" )
					DBCSetLastErrno( CDBC_ERROR_ALLOC );
					DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
					mysql_free_result( mysql_res );
					mysql_stmt_close( mysql_stmt );
					return;
				}
				mysql_query_field_set[mysql_col_index].field_type = ConvertMysqlFieldType( mysql_fields[mysql_col_index].type ) ;
				if( mysql_query_field_set[mysql_col_index].field_type == CDBC_FIELDTYPE_INVALID )
				{
					_TRACE( "  ConvertMysqlFieldType mysql_type[%d] return failed , field_name[%s]" , mysql_fields[mysql_col_index].type , mysql_query_field_set[mysql_col_index].field_name );
					DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
					DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
					mysql_free_result( mysql_res );
					mysql_stmt_close( mysql_stmt );
					return;
				}
				mysql_query_field_set[mysql_col_index].field_length = mysql_fields[mysql_col_index].length ;
				mysql_query_field_set[mysql_col_index].field_decimal_length = mysql_fields[mysql_col_index].decimals ;
				_TRACE( "  mysql_query_field_set .field_type[%d]->[%d] .field_name[%s] .field_length[%d] .field_decimal_length[%d]"
					, mysql_fields[mysql_col_index].type,mysql_query_field_set[mysql_col_index].field_type
					, mysql_query_field_set[mysql_col_index].field_name
					, mysql_query_field_set[mysql_col_index].field_length
					, mysql_query_field_set[mysql_col_index].field_decimal_length )
				
				field_buffer_length = GetMysqlFieldLength( mysql_fields[mysql_col_index].type ) ;
				if( field_buffer_length == -1 )
				{
					_TRACE( "  GetMysqlFieldLength mysql_type[%d] return[%d] , field_name[%s]" , mysql_fields[mysql_col_index].type , field_buffer_length , mysql_query_field_set[mysql_col_index].field_name )
					DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
					DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
					mysql_free_result( mysql_res );
					mysql_stmt_close( mysql_stmt );
					return;
				}
				else if( field_buffer_length == 0 )
				{
					field_buffer_length = mysql_fields[mysql_col_index].length ;
				}
				record_buffer_size += field_buffer_length ;
			}
			
			mysql_binds_array = (MYSQL_BIND *)malloc( sizeof(MYSQL_BIND) * mysql_col_count ) ;
			if( mysql_binds_array == NULL )
			{
				_TRACE( "  malloc failed , errno[%d]" , errno )
				DBCSetLastErrno( CDBC_ERROR_ALLOC );
				DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
				mysql_free_result( mysql_res );
				mysql_stmt_close( mysql_stmt );
				return;
			}
			memset( mysql_binds_array , 0x00 , sizeof(MYSQL_BIND) * mysql_col_count );
			
			record_buffer_size++;
			_TRACE( "  record_buffer_size[%d]" , record_buffer_size )
			record_buffer = (char *)malloc( record_buffer_size ) ;
			if( record_buffer == NULL )
			{
				_TRACE( "  malloc failed , errno[%d]" , errno )
				DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
				free( mysql_binds_array );
				DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
				mysql_free_result( mysql_res );
				mysql_stmt_close( mysql_stmt );
				return;
			}
			memset( record_buffer , 0x00 , record_buffer_size );
			
			null_buffer = (my_bool *)malloc( sizeof(my_bool) * mysql_col_count ) ;
			if( null_buffer == NULL )
			{
				_TRACE( "  malloc failed , errno[%d]" , errno )
				DBCSetLastErrno( CDBC_ERROR_ALLOC );
				free( mysql_binds_array );
				free( record_buffer );
				DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
				mysql_free_result( mysql_res );
				mysql_stmt_close( mysql_stmt );
				return;
			}
			memset( null_buffer , 0x00 , sizeof(my_bool) * mysql_col_count );
			
			record_buffer_offsetptr = record_buffer ;
			null_buffer_offsetptr = null_buffer ;
			for( mysql_col_index = 0 , mysql_binds_array_offsetptr = mysql_binds_array ; mysql_col_index < mysql_col_count ; mysql_col_index++ , mysql_binds_array_offsetptr++ )
			{
				mysql_binds_array_offsetptr->buffer_type = mysql_fields[mysql_col_index].type ;
				mysql_binds_array_offsetptr->buffer = record_buffer_offsetptr ;
				mysql_binds_array_offsetptr->buffer_length = mysql_fields[mysql_col_index].length ;
				mysql_binds_array_offsetptr->is_null = null_buffer_offsetptr ;
				_TRACE( "  set result buffer .mysql_type[%d] .name[%s] .buffer[%ld][%p] .buffer_length[%ld] .is_null[%p]" , mysql_binds_array_offsetptr->buffer_type , mysql_fields[mysql_col_index].name , record_buffer_offsetptr-record_buffer,mysql_binds_array_offsetptr->buffer , mysql_binds_array_offsetptr->buffer_length , mysql_binds_array_offsetptr->is_null )
				
				field_buffer_length = GetMysqlFieldLength( mysql_fields[mysql_col_index].type ) ;
				if( field_buffer_length == -1 )
				{
					_TRACE( "  GetMysqlFieldLength mysql_type[%d] return[%d]" , mysql_fields[mysql_col_index].type , field_buffer_length )
					DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
					DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
					mysql_free_result( mysql_res );
					mysql_stmt_close( mysql_stmt );
					return;
				}
				else if( field_buffer_length == 0 )
				{
					field_buffer_length = mysql_fields[mysql_col_index].length ;
				}
				record_buffer_offsetptr += field_buffer_length ;
				null_buffer_offsetptr++;
			}
			
			nret = mysql_stmt_bind_result( mysql_stmt , mysql_binds_array ) ;
			_TRACE( "  mysql_stmt_bind_result return[%d]" , nret )
			if( nret )
			{
				DBCSetLastErrno( CDBC_ERROR_BIND_PARAM );
				free( mysql_binds_array );
				free( record_buffer );
				free( null_buffer );
				DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
				mysql_free_result( mysql_res );
				mysql_stmt_close( mysql_stmt );
				return;
			}
			
			mysql_free_result( mysql_res );
		}
		
		if( mysql_row_count > 0 && query_result_set )
		{
			alloc_size = sizeof(char*) * mysql_row_count * mysql_col_count ;
			mysql_query_result_set = (char**)malloc( alloc_size ) ;
			if( mysql_query_result_set == NULL )
			{
				_TRACE( "  malloc failed , errno[%d]" , errno )
				DBCSetLastErrno( CDBC_ERROR_ALLOC );
				free( mysql_binds_array );
				free( record_buffer );
				free( null_buffer );
				DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
				return;
			}
			memset( mysql_query_result_set , 0x00 , alloc_size );
			
			for( mysql_row_index = 0 ; mysql_row_index < mysql_row_count ; mysql_row_index++ )
			{
				_TRACE( "  mysql_row_index[%lu]" , (unsigned long)mysql_row_index )
				
				nret = mysql_stmt_fetch( mysql_stmt ) ;
				_TRACE( "  mysql_stmt_fetch return[%d]" , nret )
				if( nret )
					break;
				
				for( mysql_col_index = 0 , mysql_binds_array_offsetptr = mysql_binds_array ; mysql_col_index < mysql_col_count ; mysql_col_index++ , mysql_binds_array_offsetptr++ )
				{
					set_index = mysql_col_count * mysql_row_index + mysql_col_index ;
					
					if( ! null_buffer[mysql_col_index] )
					{
						char		buf[ 256 ] = "" ;
						uint32_t	str_len ;
						
#define IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT(_MYSQL_TYPE_,_TYPE_FORMAT_,_C_TYPE_) \
	if( mysql_binds_array_offsetptr->buffer_type == _MYSQL_TYPE_ ) \
	{ \
		_TRACE( "    mysql_col_index[%lu] : mysql_type[%d] buf[%ld]["_TYPE_FORMAT_"]" , (unsigned long)mysql_col_index , mysql_binds_array_offsetptr->buffer_type , (char*)(mysql_binds_array_offsetptr->buffer)-record_buffer , *(_C_TYPE_*)(mysql_binds_array_offsetptr->buffer) ) \
		str_len = snprintf( buf , sizeof(buf)-1 , _TYPE_FORMAT_ , *(_C_TYPE_*)(mysql_binds_array_offsetptr->buffer) ) ; \
		mysql_query_result_set[set_index] = strndup( buf , str_len ) ; \
	} \

						IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_TINY , "%"PRIi8 , int8_t )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_SHORT , "%"PRIi16 , int16_t )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_LONG , "%"PRIi32 , int32_t )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_INT24 , "%"PRIi32 , int32_t )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_LONGLONG , "%"PRIi64 , int64_t )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_FLOAT , "%f" , float )
						else IF_MYSQLTYPE_THEN_ADDPEND_RESIZABLEBUFFER_AND_SET_QUERYRESULT( MYSQL_TYPE_DOUBLE , "%lf" , double )
						else if( mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_DECIMAL || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_NEWDECIMAL || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_STRING || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_VAR_STRING )
						{
							_TRACE( "    mysql_col_index[%lu] : mysql_type[%d] buf[%ld][%s]" , (unsigned long)mysql_col_index , mysql_binds_array_offsetptr->buffer_type , (char*)(mysql_binds_array_offsetptr->buffer)-record_buffer , (char*)(mysql_binds_array_offsetptr->buffer) )
							mysql_query_result_set[set_index] = strdup( mysql_binds_array_offsetptr->buffer ) ;
						}
						else if( mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_DATE || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_TIME || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_DATETIME || mysql_binds_array_offsetptr->buffer_type == MYSQL_TYPE_TIMESTAMP )
						{
							MYSQL_TIME	*my_time = (MYSQL_TIME *)(mysql_binds_array_offsetptr->buffer) ;
							char		buf[ 10+1+8 + 1 ] = "" ;
							uint32_t	str_len ;
							str_len = snprintf( buf , sizeof(buf) , "%04d-%02d-%02d %02d:%02d:%02d" , my_time->year , my_time->month , my_time->day , my_time->hour , my_time->minute , my_time->second ) ;
							mysql_query_result_set[set_index] = strndup( buf , str_len ) ;
						}
						else
						{
							_TRACE( "    mysql_col_index[%lu] : mysql_type[%d] NOT_SUPPORT" , (unsigned long)mysql_col_index , mysql_binds_array_offsetptr->buffer_type )
							DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
							free( mysql_binds_array );
							free( record_buffer );
							free( null_buffer );
							DBCFreeSqlResult( & mysql_query_field_set , & mysql_query_result_set );
							return;
						}
					}
					else
					{
						mysql_query_result_set[set_index] = NULL ;
					}
				}
			}
		}
		
		free( mysql_binds_array );
		free( record_buffer );
		free( null_buffer );
	}

	if( row_count )
		(*row_count) = mysql_row_count ;
	if( col_count )
		(*col_count) = mysql_col_count ;
	if( query_field_set )
		(*query_field_set) = mysql_query_field_set ;
	if( query_result_set )
		(*query_result_set) = mysql_query_result_set ;
	if( affected_count )
		(*affected_count) = mysql_stmt_affected_rows(mysql_stmt) ;
	
	_TRACE( "mysql_stmt_close[%p]" , mysql_stmt )
	mysql_stmt_close( mysql_stmt );
	
	return;
}

void AutoCommitTransaction( struct DatabaseConnection *db_conn , unsigned char enable_autocommit )
{
	mysql_autocommit( db_conn->mysql , enable_autocommit );
	
	return;
}

void BeginTransaction( struct DatabaseConnection *db_conn )
{
	mysql_autocommit( db_conn->mysql , 0 );
	
	return;
}

void CommitTransaction( struct DatabaseConnection *db_conn )
{
	if( mysql_commit( db_conn->mysql ) == 1 )
		DBCSetLastErrno( CDBC_ERROR_COMMITTRANSACTION );
	
	return;
}

void RollbackTransaction( struct DatabaseConnection *db_conn )
{
	if( mysql_rollback( db_conn->mysql ) == 1 )
		DBCSetLastErrno( CDBC_ERROR_ROLLBACKTRANSACTION );
	
	return;
}

